"""
Utiltiy classes used by csv runners that implements
'Smart CVS Headers'.  These classes enable more flexible
processing for import files from multiple foreign sources.

The headers are configured by a 
"""
class CSVHeaderError(RuntimeError):
	"Module exception class"
	
class CSVHeader(object):
	"""
	Simple abstraction to represent a matchable column header 
	for a csv file.  This 'Smart Header' contains a default 
	value, an alias list and a required flag along with the
	main colunm header to enable factory level edits on 
	files.
	"""
	def __init__(self, name, required, default=None, alist=[]):
		"Create a 'Smart' CSV column header"
		self.name = name
		self.default = default
		self.required = required
		
		# set up the alias match list
		self._setAliasList(alist)

	def _getStatus(self):
		return (self.default, self.required)
	
	status = property(_getStatus)
	
	def _getAliasList(self):
		return self._matchlist
	
	def _setAliasList(self, alist=[]):
		self._matchlist = [self.name.lower()] + [itm.lower() for itm in alist[:]]
		
	aliases = property(_getAliasList, _setAliasList)
		
	def match(self, hdr):
		"""
		see if this matches anywhere
		"""
		# default to no match
		rv = None

		# search
		if hdr.lower() in self._matchlist:
			rv = self.name

		# return results
		return rv
	
class CSVHeaders(object):
	"""
	"""
	def __init__(self, headers=None):
		"""
		"""
		# default to empty stuff
		self._headers = []

		# process if presented
		if headers:
			self._headers = self._parseHeaders(headers)

	def updateHeaders(self, hdrs):
		"""
		Add incoming to current list
		"""
		self._headers += self._parseHeaders(hdrs)
		
	def setHeaders(self, hdrs=None):
		"""
		Replace (or clear) the current header mappings
		"""
		if not hdrs:
			self._headers = []
			
		self._headers = self._parseHeaders(hdrs)

	def validate(self, hdrs):
		"""
		Test a set of headers against the internal list
		and return a mapping for resolving row data.
		"""
		# check for unparsed string...
		if hdrs.__class__.__name__ == 'str':
			hdrs = hdrs.split(',')
		
		# prepare collectors
		map = {}
		refs = {}
		
		# roll through list of configured headers
		for hdr in self._headers:
			matched = False
			for pos in range(len(hdrs)):
				# try to match to the passed in listing 
				pTest = hdr.match(hdrs[pos])

	 			# hit check
				if pTest: 
					matched = True
					# check for reference
					if pTest.startswith('$'):
						n, r = pTest[1:].split('.')
						refs[n] = r
					else:
						# straight maping to the field position
			 			map[pTest] = pos
			 			
			 		# look no further...
			 		break;
			 	
 			# are we good yet?
 		 	if not matched:
 		 		# last ditch effort
	 			if hdr.default != None:
	 				if hdr.default.startswith('$'):
	 					# reference parse
	 					n, r = pTest[1:].split('.')
	 					refs[n] = r
	 				else:	
	 					# use header default value (sans #)
 						map[hdr.name] = hdr.default[1:]
 						
 				elif hdr.required:
 					# Nada, we're done here...
 					raise CSVHeaderError("Required field missing [%s]" % hdr.name)

 		# resolve any references
 		for n in refs.keys():
 			r = refs[n]
 			try:
 		 		map[n] = map[r]
 		 	except:
 		 		raise CSVHeaderError("Invalid reference or referencing missing header [%s]" % r)
 			
		return map

	def parseData(self, data, fileHeaders=None, map=None):
		"""
		Parse a line of row data given a set or headers
		or a pre-existing parse created from calling
		the validate function on the fileHeaders (much 
		faster!!!).
		"""
		# did we get a map
		if not map:
			if not fileHeaders:
				# have to have one or the other
				raise CSVHeaderError("Missing parse map")
			
			# parse headers
			map = self.validate(fileHeaders)

		# now map the data
		flds = {}
		
		for k in map.keys():
			v = map[k]
			try:
				# use the mapped reference to data 
				val = data[v]
			except:
				# just use the value (optional tags)
				val = v
				
			flds[k] = ''.join(val.split("'"))
		
		# return resulting data row
		return flds
		
	def _parseHeaders(self, hdrs):
		""""
		Parse string to create headers definitions
		"""
		# parse comma separated headers list		
		hlist = []
		for hdr in hdrs.split(','):
			required = True

			if hdr.startswith('['):
				# optional header
				required = False
				if not hdr.endswith(']'):
					# invalid systax
					raise CSVHeaderError("Sytax error: missing ']' for %s " % hdr)
				
				# strip away the brakets
				hdr = hdr.strip()[1:-1]

			# check for alias list
			alist = hdr.split('::')
			
			# final sanity check
			if len(alist) < 1:
				raise CSVHeaderError("Syntax Error: Empty header")

			# ok create the header
			hdrName = alist[0]
			hobj = CSVHeader(hdrName, required)
			
			if len(alist) > 1:
				# strip header name from alias list
				alist = alist[1:]
				if alist[-1].startswith('#') or alist[-1].startswith('$'):
					# set the default value for header
					hobj.default = alist[-1]
					# and strip it off
					alist = alist[:-1]			
			else:
				alist = []

			# set the alias list
			hobj.aliases = alist
				
			# and add to result set
			hlist.append(hobj)
		
		# return result set
		return hlist
